﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace XEnamel.Python
{
	public class PythonTranslator : ExpressionVisitor
	{
		private IPythonScopeManager _context;

		public PythonTranslator()
			: base()
		{
			_context = new ScopeManager();
			_context.EnterScope();
		}

		public dynamic VisitAndCompile(IronPython.Runtime.PythonFunction func)
		{
			return DynamicMethod.Load(Visit(func) as LambdaExpression);
		}

		public Expression Visit(IronPython.Runtime.PythonFunction func)
		{
			return Visit(func.GetDefinition());
		}

		protected override Expression VisitExtension(Expression node)
		{
			if (node is IronPython.Compiler.Ast.Node)
			{
				return VisitPythonNode(node as IronPython.Compiler.Ast.Node);
			}
			return base.VisitExtension(node);
		}

		protected virtual Expression VisitPythonNode(IronPython.Compiler.Ast.Node node)
		{
			Expression expr = null;

			if (node is IronPython.Compiler.Ast.ScopeStatement)
			{
				_context.EnterScope();
			}

			if (node is IronPython.Compiler.Ast.FunctionDefinition)
			{
				expr = VisitFunctionDefinition(node as IronPython.Compiler.Ast.FunctionDefinition);
			}
			else if (node is IronPython.Compiler.Ast.Parameter)
			{
				expr = VisitPythonParameter(node as IronPython.Compiler.Ast.Parameter);
			}

			if (expr == null)
			{
				throw new NotImplementedException(node.GetType().Name);
			}

			if (node is IronPython.Compiler.Ast.ScopeStatement)
			{
				_context.LeaveScope();
			}

			return expr;
		}

		private Expression VisitFunctionDefinition(IronPython.Compiler.Ast.FunctionDefinition node)
		{
			var returnLabel = _context.CreateLabel("return");
			var parameters = node.Parameters.Select(x => Visit(x) as ParameterExpression).ToArray();

			var bodyBlock = node.Body.GetBlock();
			var body = Visit(Expression.Block(bodyBlock.Variables.Select(x => Visit(x) as ParameterExpression).ToArray(),
				new List<Expression>(bodyBlock.Expressions).Concat(new[] { Expression.Label(returnLabel, Expression.Constant(null)) })));

			return Expression.Lambda(body, parameters);
		}

		private Expression VisitPythonParameter(IronPython.Compiler.Ast.Parameter parameter)
		{
			return Visit(_context.FindSymbol(parameter.Name) ?? _context.CreateSymbol(parameter.Name));
		}

		protected override Expression VisitBinary(BinaryExpression node)
		{
			try
			{
				return base.VisitBinary(node);
			}
			catch
			{
				if (node.NodeType == ExpressionType.Assign)
				{
					return Expression.Assign(node.Left, node.Right.ChangeType(node.Left.Type).Convert(node.Left.Type));
				}
				else
				{
					return Expression.Dynamic(new SimpleBinaryOperationBinder(node.NodeType), node.Type, Visit(node.Left), Visit(node.Right));
				}
			}
		}

		protected override Expression VisitBlock(BlockExpression node)
		{
			_context.EnterScope();
			var expr = base.VisitBlock(node);
			var block = expr as BlockExpression;
			var variables = block.Variables.Select(x => Visit(x) as ParameterExpression).ToArray();
			block = base.VisitBlock(block) as BlockExpression;
			block = Expression.Block(variables, block.Expressions);
			

			_context.LeaveScope();
			return expr;
		}

		protected override Expression VisitGoto(GotoExpression node)
		{
			switch (node.Kind)
			{
				case GotoExpressionKind.Break:
					return Expression.Break(_context.FindLabel(node.Target.Name), Expression.Constant(null));
				case GotoExpressionKind.Continue:
					//return Expression.Continue(_context.FindLabel(node.Target.Name), typeof(object));
					return base.VisitGoto(node);
				case GotoExpressionKind.Goto:
					//return Expression.Goto(_context.FindLabel(node.Target.Name), Expression.Constant(null));
					return base.VisitGoto(node);
				case GotoExpressionKind.Return:
				default:
					//return Expression.Return(_context.FindLabel(node.Target.Name), Expression.Constant(null));
					return base.VisitGoto(node);
			}
			
		}

		protected override Expression VisitLabel(LabelExpression node)
		{
			return Expression.Label(_context.FindLabel(node.Target.Name) ?? _context.CreateLabel(node.Target.Name), Expression.Constant(null));
			//return base.VisitLabel(node);
		}

		protected override LabelTarget VisitLabelTarget(LabelTarget node)
		{
			return (node == null) ? base.VisitLabelTarget(node) : _context.VisitLabelTarget(node.Name);
		}

		protected override Expression VisitParameter(ParameterExpression node)
		{
			// Either pull in the Python variable, or do the default action:
			Expression expr = null;
			if (node.Name != null)
			{
				expr = _context.FindSymbol(node.Name);
			}
			if (expr == null)
			{
				expr = base.VisitParameter(node); // _context.CreateSymbol(node.Name);
			}
			return expr;
		}
	}
}